﻿using Common.FrontEnd;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Util.Props;

namespace FrontEnd.Feature
{
    /// <summary>
    /// Abstract base class for windowed feature extractors like DeltasFeatureExtractor, ConcatFeatureExtractor
    /// or S3FeatureExtractor. The main purpose of this it to collect window size cepstra frames in a buffer
    /// and let the extractor compute the feature frame with them.
    /// </summary>
    public class AbstractFeatureExtractor: BaseDataProcessor
    {
        /** The property for the window of the DeltasFeatureExtractor. */
        [S4Integer(defaultValue = 3)]
        public static String PROP_FEATURE_WINDOW = "windowSize";

        private int bufferPosition;
        private Signal pendingSignal;
        private List<IData> outputQueue = new List<IData>();

        protected int cepstraBufferEdge;
        protected int window;
        protected int currentPosition;
        protected  const int cepstraBufferSize = 256;
        protected DoubleData[] cepstraBuffer = new DoubleData[cepstraBufferSize];

        /**
        /// 
        /// @param window
         */
        public AbstractFeatureExtractor( int window ) 
        {
            this.window = window;
        }

        public AbstractFeatureExtractor() {
        }

        /*
       /// (non-Javadoc)
        *
       /// @see edu.cmu.sphinx.util.props.Configurable#newProperties(edu.cmu.sphinx.util.props.PropertySheet)
        */
        override
        public void newProperties(PropertySheet ps)
        {
            base.newProperties(ps);
            window = ps.getInt(PROP_FEATURE_WINDOW);
        }

        /// <summary>
        /// @see Sphincs.frontend.DataProcessor#initialize(Sphinx.frontend.CommonConfig)
        /// </summary>
        override public void initialize() 
        {
            base.initialize();
            cepstraBufferEdge = cepstraBufferSize - (window* 2 + 2);
            outputQueue = new List<IData>();
            reset();
        }


        /** Resets the DeltasFeatureExtractor to be ready to read the next segment of data. */
        private void reset() 
        {
            bufferPosition = 0;
            currentPosition = 0;
        }


        /// <summary>
        /// Returns the next Data object produced by this DeltasFeatureExtractor.
        /// </summary>
        /// <returns>the next available Data object, returns null if no Data is available</returns>
        override public IData getData()
        {
            if (outputQueue.Count==0) 
            {
                IData input = getNextData();
                if (input != null) 
                {
                    if (input is DoubleData) 
                    {
                        addCepstrum((DoubleData) input);
                        computeFeatures(1);
                    } 
                    else if (input is DataStartSignal) 
                    {
                        pendingSignal = null;
                        outputQueue.Add(input);
                        IData start = getNextData();
                        int n = processFirstCepstrum(start);
                        computeFeatures(n);
                        if (pendingSignal != null) 
                        {
                            outputQueue.Add(pendingSignal);
                        }
                    } 
                    else if (input is DataEndSignal || input is SpeechEndSignal) 
                    {
                        // when the DataEndSignal is right at the boundary
                        int n = replicateLastCepstrum();
                        computeFeatures(n);
                        outputQueue.Add(input);
                    }
                }
            }
            if(outputQueue.Count==0)
                return null;
            IData ret = outputQueue[0];
            outputQueue.RemoveAt(0);
            return ret;
        }


        private IData getNextData() 
        {
            IData d = getPredecessor().getData();
            while (d != null && !(d is DoubleData || d is DataEndSignal || d is DataStartSignal || d is SpeechEndSignal)) {
                outputQueue.Add(d);
                d = getPredecessor().getData();
            }

            return d;
        }


        /**
        /// Replicate the given cepstrum Data object into the first window+1 number of frames in the cepstraBuffer. This is
        /// the first cepstrum in the segment.
         *
        /// @param cepstrum the Data to replicate
        /// @return the number of Features that can be computed
        /// @throws edu.cmu.sphinx.frontend.DataProcessingException
         */
        private int processFirstCepstrum(IData cepstrum)
        {
            if (cepstrum is DataEndSignal) 
            {
                outputQueue.Add(cepstrum);
                return 0;
            } 
            else if (cepstrum is DataStartSignal) 
            {
                throw new Exception("Too many UTTERANCE_START");
            } 
            else {
                // At the start of an utterance, we replicate the first frame
                // into window+1 frames, and then read the next "window" number
                // of frames. This will allow us to compute the delta-
                // double-delta of the first frame.
                //Arrays.fill(cepstraBuffer, 0, window + 1, cepstrum);
                for(int i=0;i<window + 1;i++)
                    cepstraBuffer[i]=(DoubleData)cepstrum;
                bufferPosition = window + 1;
                bufferPosition %= cepstraBufferSize;
                currentPosition = window;
                currentPosition %= cepstraBufferSize;
                int numberFeatures = 1;
                pendingSignal = null;
                for (int i = 0; i < window; i++) {
                    IData next = getNextData();
                    if (next != null) {
                        if (next is DoubleData) {
                            // just a cepstra
                            addCepstrum((DoubleData) next);
                        } else if (next is DataEndSignal || next is SpeechEndSignal) {
                            // end of segment cepstrum
                            pendingSignal = (Signal) next;
                            replicateLastCepstrum();
                            numberFeatures += i;
                            break;
                        } else if (next is DataStartSignal) {
                            throw new Exception("Too many UTTERANCE_START");
                        }
                    }
                }
                return numberFeatures;
            }
        }


        /**
        /// Adds the given DoubleData object to the cepstraBuffer.
         *
        /// @param cepstrum the DoubleData object to add
         */
        private void addCepstrum(DoubleData cepstrum) 
        {
            cepstraBuffer[bufferPosition++] = cepstrum;
            bufferPosition %= cepstraBufferSize;
        }


        /**
        /// Replicate the last frame into the last window number of frames in the cepstraBuffer.
         *
        /// @return the number of replicated Cepstrum
         */
        private int replicateLastCepstrum() {
            DoubleData last;
            if (bufferPosition > 0) {
                last = cepstraBuffer[bufferPosition - 1];
            } else if (bufferPosition == 0) {
                last = cepstraBuffer[cepstraBuffer.Length - 1];
            } else {
                throw new Exception("BufferPosition < 0");
            }
            for (int i = 0; i < window; i++) {
                addCepstrum(last);
            }
            return window;
        }


        /**
        /// Converts the Cepstrum data in the cepstraBuffer into a FeatureFrame.
         *
        /// @param totalFeatures the number of Features that will be produced
         */
        private void computeFeatures(int totalFeatures) {
            getTimer().start();
            if (totalFeatures == 1) {
                computeFeature();
            } else {
                // create the Features
                for (int i = 0; i < totalFeatures; i++) {
                    computeFeature();
                }
            }
            getTimer().stop();
        }


        /** Computes the next Feature. */
        private void computeFeature() {
            IData feature = computeNextFeature();
            outputQueue.Add(feature);
        }


        /**
        /// Computes the next feature. Advances the pointers as well.
         *
        /// @return the feature Data computed
         */
        virtual public IData computeNextFeature() 
        {
            throw new NotImplementedException("computeNextFeature");
        }
    }
}
